package com.framework.oauthclient.ext;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;

/**
 * @author zzg
 */
public class ClientBearerTokenAuthenticationFilter extends OncePerRequestFilter {
    private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
    private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
    private AuthenticationManager authenticationManager = new ClientAuthenticationManager();
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    private AccessDecisionManager accessDecisionManager;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        FilterInvocation filterInvocation = new FilterInvocation(request, response, filterChain);
        // web security 里面已经有权限（静态资源）的这里不再过滤
        if (attemptAuthorization(filterInvocation)) {
            filterChain.doFilter(request, response);
            return;
        }

        String token;
        try {
            token = this.bearerTokenResolver.resolve(request);
        } catch (OAuth2AuthenticationException invalid) {
            this.logger.trace("Sending to authentication entry point since failed to resolve bearer token", invalid);
            this.authenticationEntryPoint.commence(request, response, invalid);
            return;
        }

        if (token == null) {
            OAuth2AuthenticationException invalid = new OAuth2AuthenticationException("请求头没有设置bearer token");
            this.authenticationEntryPoint.commence(request, response, invalid);
        }

        // 从httpSession中获取权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        // 已经有权限
        if (authentication instanceof ClientAuthenticationToken) {
            filterChain.doFilter(request, response);
            return;
        }
        // 如果httpSession中没有权限，从redis中读取token，userinfo
        BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token);
        authenticationManager.authenticate(authenticationRequest);

        filterChain.doFilter(request, response);
    }

    private boolean attemptAuthorization(FilterInvocation filterInvocation) {
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (!authentication.isAuthenticated()) {
                authentication = this.authenticationManager.authenticate(authentication);
            }
            Collection<ConfigAttribute> attributes = securityMetadataSource.getAttributes(filterInvocation);
            accessDecisionManager.decide(authentication, filterInvocation, attributes);
            return true;
        } catch (AccessDeniedException ex) {
            return false;
        }
    }

    public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
        this.securityMetadataSource = newSource;
    }

    public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
        this.accessDecisionManager = accessDecisionManager;
    }
}
